﻿#region MIT License

//The MIT License (MIT)
//Copyright (c) 2013 PTA GmbH
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
//to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
//and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
//WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

#endregion

using System;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.ServiceModel;
using Microsoft.Xrm.Sdk;

namespace Pta.TrueUniqueAutonumberCreator.Plugins
{
    /// <summary>
    ///     Base class for all Plugins.
    /// </summary>
    public class Plugin : IPlugin
    {
        internal enum PluginStage
        {
            PreValidation = 10,
            PreOperation = 20,
            PostOperation = 40
        };

        protected readonly string SecureConfig = null;
        protected readonly string UnsecureConfig = null;

        private Collection<Tuple<int, string, string, Action<LocalPluginContext>>> _registeredEvents;

        /// <summary>
        ///     Constructor of the plugin
        ///     See http://gonzaloruizcrm.blogspot.de/2011/09/adding-configuration-to-plugins-in-crm.html
        /// </summary>
        public Plugin(string unsecureConfig, string secureConfig)
        {
            SecureConfig = secureConfig;
            UnsecureConfig = unsecureConfig;
        }

        /// <summary>
        ///     Gets the List of events that the plug-in should fire for. Each List
        ///     Item is a <see cref="System.Tuple" /> containing the Pipeline Stage, Message and (optionally) the Primary Entity.
        ///     In addition, the fourth parameter provide the delegate to invoke on a matching registration.
        /// </summary>
        protected Collection<Tuple<int, string, string, Action<LocalPluginContext>>> RegisteredEvents
        {
            get
            {
                if (_registeredEvents == null)
                {
                    _registeredEvents = new Collection<Tuple<int, string, string, Action<LocalPluginContext>>>();
                }

                return _registeredEvents;
            }
        }

        /// <summary>
        ///     Executes the plug-in.
        /// </summary>
        /// <param name="serviceProvider">The service provider.</param>
        /// <remarks>
        ///     For improved performance, Microsoft Dynamics CRM caches plug-in instances.
        ///     The plug-in's Execute method should be written to be stateless as the constructor
        ///     is not called for every invocation of the plug-in. Also, multiple system threads
        ///     could execute the plug-in at the same time. All per invocation state information
        ///     is stored in the context. This means that you should not use global variables in plug-ins.
        /// </remarks>
        public void Execute(IServiceProvider serviceProvider)
        {
            if (serviceProvider == null)
            {
                throw new ArgumentNullException("serviceProvider");
            }

            // Construct the Local plug-in context.
            var localcontext = new LocalPluginContext(serviceProvider);

            localcontext.Trace(string.Format(CultureInfo.InvariantCulture, "Entered {0}.Execute()", GetType()));

            try
            {
                // Iterate over all of the expected registered events to ensure that the plugin
                // has been invoked by an expected event
                // For any given plug-in event at an instance in time, we would expect at most 1 result to match.
                Action<LocalPluginContext> entityAction =
                    (from a in RegisteredEvents
                     where (
                               a.Item1 == localcontext.PluginExecutionContext.Stage &&
                               a.Item2 == localcontext.PluginExecutionContext.MessageName &&
                               (string.IsNullOrWhiteSpace(a.Item3)
                                    ? true
                                    : a.Item3 == localcontext.PluginExecutionContext.PrimaryEntityName)
                           )
                     select a.Item4).FirstOrDefault();

                if (entityAction != null)
                {
                    localcontext.Trace(string.Format(
                        CultureInfo.InvariantCulture,
                        "{0} is firing for Entity: {1}, Message: {2}",
                        GetType(),
                        localcontext.PluginExecutionContext.PrimaryEntityName,
                        localcontext.PluginExecutionContext.MessageName));

                    entityAction.Invoke(localcontext);

                    // now exit - if the derived plug-in has incorrectly registered overlapping event registrations,
                    // guard against multiple executions.
// ReSharper disable RedundantJumpStatement
                    return;
// ReSharper restore RedundantJumpStatement
                }
            }
            catch (FaultException<OrganizationServiceFault> e)
            {
                localcontext.Trace(string.Format(CultureInfo.InvariantCulture, "Exception: {0}", e));

                // Handle the exception.
                throw;
            }
            finally
            {
                localcontext.Trace(string.Format(CultureInfo.InvariantCulture, "Exiting {0}.Execute()", GetType()));
            }
        }

        protected Object GetTarget(LocalPluginContext localContext)
        {
            if (localContext == null)
            {
                throw new ArgumentNullException("localContext");
            }

            if (localContext.PluginExecutionContext.InputParameters == null
                || !localContext.PluginExecutionContext.InputParameters.Contains("Target"))
                return null;

            return localContext.PluginExecutionContext.InputParameters["Target"];
        }

        protected class LocalPluginContext
        {
            internal LocalPluginContext(IServiceProvider serviceProvider)
            {
                if (serviceProvider == null)
                {
                    throw new ArgumentNullException("serviceProvider");
                }

                ServiceProvider = serviceProvider;

                // Obtain the execution context service from the service provider.
                PluginExecutionContext =
                    (IPluginExecutionContext) serviceProvider.GetService(typeof (IPluginExecutionContext));

                // Obtain the tracing service from the service provider.
                TracingService = (ITracingService) serviceProvider.GetService(typeof (ITracingService));

                // Obtain the Organization Service factory service from the service provider
                var factory =
                    (IOrganizationServiceFactory) serviceProvider.GetService(typeof (IOrganizationServiceFactory));

                // Use the factory to generate the Organization Service.
                OrganizationService = factory.CreateOrganizationService(PluginExecutionContext.UserId);
            }

            internal IServiceProvider ServiceProvider { get; private set; }

            internal IOrganizationService OrganizationService { get; private set; }

            internal IPluginExecutionContext PluginExecutionContext { get; private set; }

            internal ITracingService TracingService { get; private set; }

            internal void Trace(string message)
            {
                if (string.IsNullOrWhiteSpace(message) || TracingService == null)
                {
                    return;
                }

                if (PluginExecutionContext == null)
                {
                    TracingService.Trace(message);
                }
                else
                {
                    TracingService.Trace(
                        "{0}, Correlation Id: {1}, Initiating User: {2}",
                        message,
                        PluginExecutionContext.CorrelationId,
                        PluginExecutionContext.InitiatingUserId);
                }
            }
        }
    }
}